home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
2257
/
2257.xpi
/
chrome
/
content
/
TextEditorLib.js
< prev
Wrap
Text File
|
2006-03-21
|
18KB
|
513 lines
/**
* TextEditorLib library class takes over typing functions
* of a browser that supports javascript1.5, it handles
* multiple TextNodes at the same time by storing text node references in a map.
* Thanks to Alex Benenson and Roman Mironenko
*/
var TextEditorLib = {
// this map will contain all TextNodes.
textNodeMap : new Array(),
isCapsOn : false,
nodeId : 1,
areAnyTextEditorsAttached : function() {
if (!TextEditorLib.isObjectEmpty(this.textNodeMap)) {
for(var i=0;i<TextEditorLib.textNodeMap.length;i++) {
var txtNode = TextEditorLib.textNodeMap[i];
if (!TextEditorLib.isObjectEmpty(txtNode)) {
var attr = txtNode.txtnode.getAttribute("TextEditorLib.attribute.id");
if (!TextEditorLib.isObjectEmpty(attr)) {
return true;
}
}
}
}
return false;
},
isObjectEmpty : function(obj) {
var r;
try {
r= obj==undefined || obj==null || obj.toString()=="";
} catch (e) {
r=true;
}
return r;
},
isNodeEditor : function(node) {
return node.getEditor != undefined || node.contentDocument != undefined;
},
toggleEditor : function(func) {
if (func==undefined || func==null) {
throw "Translation function undefined.";
}
var node = TextEditorLib.getNode();
if (node==null) {return null;}
var attr = node.getAttribute("TextEditorLib.attribute.id");
if (TextEditorLib.isObjectEmpty(attr)) {
attr=0;
}
var textNode = TextEditorLib.textNodeMap[attr];
if (textNode==undefined || TextEditorLib.isObjectEmpty(attr)) {
// define new text node
attr = this.nodeId;
node.setAttribute("TextEditorLib.attribute.id",attr);
this.nodeId++;
textNode = new TextNode.TextNode(node, "keypress", TextEditorLib.onKeyHandler, func, "keyup", TextEditorLib.handleCapsLockPressed, TextEditorLib.isNodeEditor(node));
textNode.setUp();
TextEditorLib.textNodeMap[attr]=textNode;
} else {
// destroy existing text node
textNode.tearDown();
delete TextEditorLib.textNodeMap[attr];
node.setAttribute("TextEditorLib.attribute.id",null);
}
},
getTextNode : function() {
var node = TextEditorLib.getNode();
var attr = node.getAttribute("TextEditorLib.attribute.id");
if (TextEditorLib.isObjectEmpty(attr)) {
attr=0;
}
return TextEditorLib.textNodeMap[attr];
},
// try to initialize this text node with a focused node from the document.
getNode : function() {
var node = document.commandDispatcher.focusedElement;
if (node==null) {
if (document.commandDispatcher.focusedWindow) {
if (document.commandDispatcher.focusedWindow.document) {
// find ThunderBird compose window (oh, yes, this is riduculous.)
var editors = document.getElementsByTagName("editor");
for (var i = 0; i < editors.length; i++) {
if (editors[i].contentWindow == document.commandDispatcher.focusedWindow) {
return editors[i];
}
}
}
node = document.commandDispatcher.focusedWindow.frameElement;
if (node && node.contentDocument && node.contentDocument.designMode=="on") {
return node; // midas in iframe.
}
}
} else {
var name = node.localName.toUpperCase();
var type = node.type;
if (name=="TEXTAREA" || name=="TEXTBOX" || (name=="INPUT" && (type=="text" || type=="file"))) {
if (!node.disabled && !node.readOnly) {
return node;
}
}
}
return null;
},
isEnterPressed : function(key) {
if (key==13) {
return true;
}
return false;
},
handleCapsLockPressed : function(event) {
if (event.which==20) {
if (TextEditorLib.isCapsOn==false) {
TextEditorLib.isCapsOn=true;
} else {
TextEditorLib.isCapsOn=false;
}
}
},
isCapitalLetter : function(isShiftOn) {
var isCapital=TextEditorLib.isCapsOn;
if (isShiftOn) {
if (isCapital) {
isCapital=false;
} else {
isCapital=true;
}
}
return isCapital;
},
onKeyHandler : function(event) {
// if this event was already processed by this handler, do not process it again
if (event.keyCode == 255 && event.charCode > 0) {
// I can't explain why this needs to be done, but midas will fail
// to process event otherwise.
if (event.target.nodeName == "HTML")
for (var i in event) if (("" + i).toUpperCase()) event[i];
return;
}
if (event.charCode > 0 && !event.ctrlKey && !event.altKey && !event.metaKey) {
var txtnode = TextEditorLib.getTextNode();
// is shift on, is typed letter capital?
var isShiftOn = event.shiftKey;
var isCapital=TextEditorLib.isCapitalLetter(isShiftOn);
// translate typed character
var translator = txtnode.translationFunction;
var ch = translator(event.which, isCapital, isShiftOn);
if (ch==-1) {
return;
}
event.preventDefault();
// in case if the return value is an array, print each element of the array in sequence.
if (ch instanceof Array) {
for(var i=0;i<ch.length;i++) {
var newEvent = document.createEvent("KeyEvents");
newEvent.initKeyEvent(event.type, event.canBubble, event.cancelable, event.view, false, false, false, false, 255, ch[i]);
event.target.dispatchEvent(newEvent);
}
} else {
var newEvent = document.createEvent("KeyEvents");
newEvent.initKeyEvent(event.type, event.canBubble, event.cancelable, event.view, false, false, false, false, 255, ch);
event.target.dispatchEvent(newEvent);
}
}
},
// transformer functions
getSelection : function(node) {
if (node==null) {
// retrieve global selection
var window = document.commandDispatcher.focusedWindow;
if (window && window.getSelection) {
return window.getSelection();
}
} else {
// return selection from editor or from midas content window
return node.getSelection ? node.getSelection() : node.contentWindow.getSelection();
}
return null;
},
splitHtmlString: function(string) {
var re = /<[\/]?[!A-Z][^>]*>/ig;
var result = new Array();
var lastIndex = 0;
var arr = null;
while ( (arr = re.exec(string)) != null) {
result[result.length] = string.substring(lastIndex, arr.index);
result[result.length] = string.substring(arr.index, re.lastIndex);
lastIndex = re.lastIndex;
}
result[result.length] = string.substr(lastIndex);
return result;
},
convertWithHTML: function(src, skipHtml, converter) {
if (src == "" || src == null) return src;
if (!skipHtml) {
return converter(src);
} else {
var arr = TextEditorLib.splitHtmlString(src);
for (var i = 0; i < arr.length; i++) {
if ( (i % 2) == 0) arr[i] = converter(arr[i]);
}
return arr.join("");
}
},
insertNodeAtSelection : function(selection, insertNode)
{
var range = selection.getRangeAt(0);
selection.removeAllRanges();
range.deleteContents();
var container = range.startContainer;
var pos = range.startOffset;
if (!pos) {
var newNode = document.createTextNode(" ");
range.insertNode(newNode);
selection.addRange(range);
range = selection.getRangeAt(0);
selection.removeAllRanges();
range.deleteContents();
container = range.startContainer;
pos = range.startOffset;
}
range=document.createRange();
// insertion logic
// special case inserting text into text node
if (container.nodeType==insertNode.TEXT_NODE && insertNode.nodeType==insertNode.TEXT_NODE) {
container.insertData(pos, insertNode.nodeValue);
// set cursor position at the end
range.setEnd(container, pos+insertNode.length);
range.setStart(container, pos+insertNode.length);
} else {
var afterNode;
if (container.nodeType==insertNode.TEXT_NODE) {
// inserting into a textnode, create 2 new nodes
// insert the new node inbetween
var textNode = container;
container = textNode.parentNode;
var text = textNode.nodeValue;
// text before the split
var textBefore = text.substr(0,pos);
// text after the split
var textAfter = text.substr(pos);
var beforeNode = document.createTextNode(textBefore);
afterNode = document.createTextNode(textAfter);
// insert the all new nodes before the old one
container.insertBefore(afterNode, textNode);
container.insertBefore(insertNode, afterNode);
container.insertBefore(beforeNode, insertNode);
// remove the old node
container.removeChild(textNode);
} else {
// else simply insert the node
afterNode = container.childNodes[pos];
container.insertBefore(insertNode, afterNode);
}
// set cursor
range.setEnd(afterNode, 0);
range.setStart(afterNode, 0);
}
selection.addRange(range);
},
isTransformableNodeType : function(node) {
return node.nodeType == node.TEXT_NODE || node.nodeType == node.PROCESSING_INSTRUCTION_NODE || node.nodeType == node.COMMENT_NODE;
},
isBeginningOfTransformableContainer : function(range, node, state) {
var startContainer = range.startContainer;
if (!state.started &&
((TextEditorLib.isTransformableNodeType(startContainer) && node == state.range.startContainer) ||
(startContainer.childNodes && node == startContainer.childNodes[range.startOffset]))) {
return true;
}
return false;
},
isEndOfTransformableContainer : function(range, node, state) {
var endContainer = range.endContainer;
if (!state.finished &&
((TextEditorLib.isTransformableNodeType(endContainer) && node == endContainer) || ((endContainer.childNodes.length > 0) &&
node == endContainer.childNodes[state.range.endOffset - 1]))) {
return true;
}
return false;
},
NodeRangeConversionState : function(state, node) {
this.node = node;
this.range = state.range;
this.convert = state.convert;
this.isAsynchronous = state.isAsynchronous;
this.started = state.started;
this.finished = state.finished;
this.converted = state.converted;
this.toString = function() {
return "started : " + this.started + ", finished: " + this.finished;
};
},
transformNodeText : function(node, state) {
var start = (node == state.range.startContainer) ? state.range.startOffset : 0;
var end = (node == state.range.endContainer) ? state.range.endOffset : node.nodeValue.length;
var remainder = (node == state.range.endContainer) ? node.nodeValue.length - state.range.endOffset : 0;
var convertedValue;
if (state.isAsynchronous) {
var stateCopy = new TextEditorLib.NodeRangeConversionState(state, node);
// call asynchronous conversion function, pass it the text to be converted and
// copy of the state, so that the conversion function can initiate conversion, return
// and at the end of conversion make a call back to this function with the copied state
// in synchronous mode.
var conversionResult = state.convert(node.nodeValue.substring(start, end), stateCopy);
// if conversionResult is not null, then the call was resolved synchronously (from cache,)
// and there will be no asynchronous call back.
if (conversionResult!=null) {
convertedValue = node.nodeValue.substring(0, start) + conversionResult + node.nodeValue.substr(end);
}
} else {
// call synchronous conversion function.
convertedValue = node.nodeValue.substring(0, start) + state.convert(node.nodeValue.substring(start, end)) + node.nodeValue.substr(end);
}
state.converted = true;
if (!TextEditorLib.isObjectEmpty(convertedValue)) {
node.nodeValue = convertedValue;
if (node == state.range.endContainer) {
state.range.setEnd(node, node.nodeValue.length - remainder);
}
if (node == state.range.startContainer) {
state.range.setStart(node, start);
}
}
},
RangeConversionState: function(range, converter, isAsynchronous) {
this.range = range;
this.convert = converter;
this.isAsynchronous = isAsynchronous;
this.started = false;
this.finished = false;
this.converted = false;
this.toString = function() {
return "started : " + this.started + ", finished: " + this.finished;
};
},
transformRangeNode : function(node, state) {
if (state.started && state.finished) {
return;
}
if (TextEditorLib.isBeginningOfTransformableContainer(state.range, node, state)) {
state.started = true;
}
if (TextEditorLib.isTransformableNodeType(node)) {
if (state.started && !state.finished) {
TextEditorLib.transformNodeText(node, state);
}
} else if (node.childNodes) {
for (var i = 0; i < node.childNodes.length; i++) {
TextEditorLib.transformRangeNode(node.childNodes[i], state);
if (state.started && state.finished)
break;
}
}
if (TextEditorLib.isEndOfTransformableContainer(state.range, node, state)) {
state.finished = true;
}
},
transformHTMLSelection : function(selection, transformerFunc, isAsynchronous) {
if (selection==null || TextEditorLib.isObjectEmpty(transformerFunc)) {
return;
}
for (var i=0;i<selection.rangeCount;i++) {
var conversionState = new TextEditorLib.RangeConversionState(selection.getRangeAt(i), transformerFunc, isAsynchronous);
TextEditorLib.transformRangeNode(selection.getRangeAt(i).commonAncestorContainer, conversionState);
if (!conversionState.converted && selection.getRangeAt(i).toString().length>0) {
// extra conversion handling
var range = selection.getRangeAt(i);
var newNode = document.createTextNode(transformerFunc(range.toString()));
TextEditorLib.insertNodeAtSelection(selection, newNode);
}
}
},
transformSelectionText : function(transformerFunc, skipHtml, isAsynchronous) {
var node = TextEditorLib.getNode();
if (node!=null || !TextEditorLib.isObjectEmpty(TextEditorLib.getSelection(null))) {
if (node==null || TextEditorLib.isNodeEditor(node)) {
// transform text in an HTML document or HTML editor
TextEditorLib.transformHTMLSelection(TextEditorLib.getSelection(node), transformerFunc, isAsynchronous);
} else if (node!=null) {
// transform text in a text input field or textarea
var oldValue = node.value;
var selStart = node.selectionStart;
var selEnd = node.selectionEnd;
var value = oldValue.substring(selStart, selEnd);
var newValue = TextEditorLib.convertWithHTML(value, skipHtml, transformerFunc);
var scrollTop = node.scrollTop;
node.value = oldValue.substring(0, selStart) + newValue + oldValue.substring(selEnd, oldValue.length);
node.selectionStart = selStart + value.length;
node.selectionEnd = selStart + value.length;
}
}
}
}
/**
* TextNode class is used to hold reference to a text editor field.
* TextNode can initialize itself from a document node, it handles
* event listeners on the editor field.
*/
var TextNode = {
//txtnode : undefined,
//eventToListen : undefined,
//translationFunction : undefined,
TextNode : function(node, onKeyEventToListen, onKeyHandlerFunc, translationFunc, onCapsLockEventToListen, onCapsLockHandlerFunc, isNodeEditor) {
this.txtnode = node;
this.eventToListen = onKeyEventToListen;
this.onKeyHandlerFunc = onKeyHandlerFunc;
this.translationFunction = translationFunc;
this.onCapsLockEventToListen = onCapsLockEventToListen;
this.onCapsLockHandlerFunc = onCapsLockHandlerFunc;
this.isNodeEditor = isNodeEditor;
this.borderStyle = null;
this.backgroundColor = null;
// attach object's methods
this.getNode = TextNode.getNode;
this.setUp = TextNode.setUp;
this.tearDown = TextNode.tearDown;
this.attachListener = TextNode.attachListener;
this.detachListener = TextNode.detachListener;
},
getTranslationFunction : function() {
return this.translationFunction;
},
getNode : function() {
return this.txtnode;
},
setUp : function() {
this.attachListener(this.eventToListen, this.onKeyHandlerFunc);
this.attachListener(this.onCapsLockEventToListen, this.onCapsLockHandlerFunc);
// set some stupid style parameters.
var style = "dashed 1px red";
if (this.isNodeEditor) {
this.borderStyle = this.txtnode.style.border;
this.txtnode.style.border = style;
} else {
this.borderStyle = this.txtnode.style.outline;
this.txtnode.style.outline = style;
}
this.backgroundColor = document.defaultView.getComputedStyle(this.txtnode, "").getPropertyValue("background-color");
var newRGB = getModifiedColor(this.backgroundColor, -15,0,-15);
this.txtnode.style.backgroundColor=newRGB;
},
tearDown : function() {
this.detachListener(this.eventToListen, this.onKeyHandlerFunc);
this.detachListener(this.onCapsLockEventToListen, this.onCapsLockHandlerFunc);
// unset some stupid style parameters.
this.txtnode.style.backgroundColor=this.backgroundColor;
if (this.isNodeEditor) {
this.txtnode.style.border = this.borderStyle;
} else {
this.txtnode.style.outline = this.borderStyle;
}
},
// attaches a listener to the txtnode, registers func to process, false - for bubbling
attachListener : function(eventToListen, onKeyHandlerFunc) {
if (eventToListen == undefined || onKeyHandlerFunc == undefined) {
throw "Cannot attach listener.";
}
if (this.txtnode.contentDocument) {
this.txtnode.contentDocument.documentElement.addEventListener(eventToListen, onKeyHandlerFunc, false);
} else {
this.txtnode.addEventListener(eventToListen, onKeyHandlerFunc, false);
}
},
// detaches previously attached listener from the txtnode
detachListener : function(eventToListen, onKeyHandlerFunc) {
if (eventToListen == undefined || onKeyHandlerFunc == undefined) {
throw "Cannot detach listener.";
}
if (this.txtnode.contentDocument) {
this.txtnode.contentDocument.documentElement.removeEventListener(eventToListen, onKeyHandlerFunc, false);
} else {
this.txtnode.removeEventListener(eventToListen, onKeyHandlerFunc, false);
}
}
}